package com.moli.iov.alarm.service.impl;

import static com.moli.iov.alarm.constants.Constants.END_TIME_RANGE;
import static com.moli.iov.alarm.constants.Constants.START_TIME_RANGE;

import com.moli.iov.alarm.enums.AlarmTabEnum;
import com.moli.iov.alarm.enums.HandlingStatusEnum;
import com.moli.iov.alarm.model.dto.AlarmLocationDto;
import com.moli.iov.alarm.model.dto.AlarmMileageInfoDto;
import com.moli.iov.alarm.model.dto.AlarmVehicleInfoDto;
import com.moli.iov.alarm.model.dto.req.AlarmInfoReqDto;
import com.moli.iov.alarm.model.dto.req.AlarmMeAuditReqDto;
import com.moli.iov.alarm.model.dto.res.AlarmInfoResDto;
import com.moli.iov.alarm.model.po.AlarmInfo;
import com.moli.iov.alarm.model.po.AlarmLocation;
import com.moli.iov.alarm.model.po.extend.AlarmMileageExceptionExtend;
import com.moli.iov.alarm.model.po.extend.FenceInfoExtend;
import com.moli.iov.alarm.repositories.AlarmDao;
import com.moli.iov.alarm.service.IAlarmService;
import com.moli.iov.base.util.CollectionUtils;
import com.moli.iov.handle.alarm.enums.AlarmCodeEnum;
import com.moli.iov.handle.alarm.enums.AlarmStatusEnum;
import com.moli.iov.model.dto.req.PageExtend;
import com.moli.iov.util.CommonUtils;
import com.moli.iov.util.DateUtil;
import com.moli.iov.util.StringUtil;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

/**
 * <p>
 *
 * </p>
 *
 * @author zql
 * @since 2019/9/3 13:20
 */
@Slf4j
@Service
public class AlarmServiceImpl implements IAlarmService {

    @Autowired
    private AlarmDao alarmDao;

    @Autowired
    protected MongoTemplate mongoTemplate;

    /**
     * 分页查询报警列表
     *
     * @param alarmInfoReqDto
     * @return
     */
    @Override
    public PageExtend<AlarmInfoResDto> listPageAlarmInfo(AlarmInfoReqDto alarmInfoReqDto) {

        PageExtend<AlarmInfo> page = new PageExtend<>();
        page.setPageNumber(alarmInfoReqDto.getPageNum());
        page.setPageSize(alarmInfoReqDto.getPageSize());

        page = alarmDao.findPage(page, assemblyConditions(alarmInfoReqDto));

        return convert(page);
    }

    /**
     * 查询当前获取正在报警列表
     *
     * @param alarmInfoDto
     * @return
     */
    @Override
    public List<AlarmInfoResDto> listAlarmInfo(AlarmInfoReqDto alarmInfoDto) {
        List<AlarmInfo> alarmInfos =
                alarmDao.find(assemblyConditions(alarmInfoDto));

        return convertDto(alarmInfos);
    }

    /**
     * 报警详情
     *
     * @param alarmInfoReqDto
     * @return
     */
    @Override
    public AlarmInfoResDto getAlarmInfo(AlarmInfoReqDto alarmInfoReqDto) {
        List<AlarmInfo> alarmInfos = alarmDao.find(assemblyConditions(alarmInfoReqDto));
        List<AlarmInfoResDto> alarmInfoResDtos = convertDto(alarmInfos);
        return alarmInfoResDtos.get(0);
    }

    /**
     * 忽略报警
     *
     * @param qlarmInfoReqDto
     */
    @Override
    public void ignore(AlarmInfoReqDto qlarmInfoReqDto) {
        Query query = new Query(Criteria.where("alarmId").is(qlarmInfoReqDto.getAlarmId()));
        Update update = new Update()
                .set("status", qlarmInfoReqDto.getStatus())
                .set("ignoreUserId", qlarmInfoReqDto.getIgnoreUserId())
                .set("ignoreUserName", qlarmInfoReqDto.getIgnoreUserName())
                .set("ignoreMemo", qlarmInfoReqDto.getIgnoreMemo())
                .set("ignoreTime", new Date())
                .set("isHandle", qlarmInfoReqDto.getIsHandle());
        alarmDao.update(query, update);
    }

    /**
     * 正在报警列表 第三方调用
     *
     * @param alarmInfoReqDto
     * @return
     */
    @Override
    public List<AlarmInfo> listAlarmInfoOfThird(AlarmInfoReqDto alarmInfoReqDto) {

        return alarmDao.find(assemblyConditions(alarmInfoReqDto));
    }

    /**
     * 组装条件
     *
     * @param alarmInfoDto
     * @return
     */
    private Query assemblyConditions(AlarmInfoReqDto alarmInfoDto) {
        Criteria criteria = new Criteria();

        Boolean setStatus = false;
        if (alarmInfoDto.getTabType() != null) {
            switch (AlarmTabEnum.getEnum(alarmInfoDto.getTabType())) {
                case UNTREATED:
                    criteria.and("isHandle").exists(false);
                    criteria.and("status").is(AlarmStatusEnum.ALARMING.getFlag());
                    setStatus = true;
                    break;
                case PROCESSING:
                    criteria.and("isHandle").is(true);
                    criteria.and("status").ne(AlarmStatusEnum.ALARM_END);
                    setStatus = true;
                    break;
                case DISMISSED:
                    criteria.and("status").is(AlarmStatusEnum.ALARM_END.getFlag());
                    setStatus = true;
                    break;
                case ALL://查询全部报警
                default:
                    break;
            }
        }

        if (StringUtil.isNotBlank(alarmInfoDto.getAlarmId())) {
            criteria.and("alarmId").is(alarmInfoDto.getAlarmId());
        }

        if (StringUtil.isNotBlank(alarmInfoDto.getVin())) {
            criteria.and("vin").is(alarmInfoDto.getVin());
        } else if (alarmInfoDto.getVins() != null && !alarmInfoDto.getVins().isEmpty()) {
            criteria.and("vin").in(alarmInfoDto.getVins());
        }

        if (StringUtil.isNotBlank(alarmInfoDto.getLicense())) {
            criteria.and("license").is(alarmInfoDto.getLicense());
        }

        if (!setStatus) {
            if (alarmInfoDto.getStatus() != null) {
                criteria.and("status").is(alarmInfoDto.getStatus());
            } else if (alarmInfoDto.getStatues() != null && !alarmInfoDto.getStatues().isEmpty()) {
                criteria.and("status").in(alarmInfoDto.getStatues());
            }
        }

        if (alarmInfoDto.getAlarmCode() != null) {
            criteria.and("alarmCode").is(alarmInfoDto.getAlarmCode());
        } else if (alarmInfoDto.getAlarmCodes() != null && !alarmInfoDto.getAlarmCodes().isEmpty()) {
            criteria.and("alarmCode").in(alarmInfoDto.getAlarmCodes());
        }

        if (alarmInfoDto.getBeginTime() != null &&
                alarmInfoDto.getEndTime() != null) {
            Date beginTime = null, endTime = null;
            try {

                beginTime = DateUtil.parser(DateUtil.YEAR_SECOND, String.format("%s %s", DateUtil.format(DateUtil.YEAR_DAY, alarmInfoDto.getBeginTime()), START_TIME_RANGE));
                endTime = DateUtil.parser(DateUtil.YEAR_SECOND, String.format("%s %s", DateUtil.format(DateUtil.YEAR_DAY, alarmInfoDto.getEndTime()), END_TIME_RANGE));
            } catch (ParseException e) {
                log.error("Current date parse find a fail, {}", e);
            }
            criteria.andOperator(Criteria.where("beginTime")
                    .gte(beginTime)
                    .lte(endTime));
        } else if (alarmInfoDto.getBeginTime() != null) {
            Date beginTime = null;
            try {
                beginTime = DateUtil.parser(DateUtil.YEAR_SECOND, String.format("%s %s", DateUtil.format(DateUtil.YEAR_DAY, alarmInfoDto.getBeginTime()), START_TIME_RANGE));
            } catch (ParseException e) {
                log.error("Current date parse find a fail, {}", e);
            }
            criteria.andOperator(Criteria.where("beginTime")
                    .gte(beginTime));
        } else if (alarmInfoDto.getEndTime() != null) {
            Date endTime = null;
            try {
                endTime = DateUtil.parser(DateUtil.YEAR_SECOND, String.format("%s %s", DateUtil.format(DateUtil.YEAR_DAY, alarmInfoDto.getEndTime()), END_TIME_RANGE));
            } catch (ParseException e) {
                log.error("Current date parse find a fail, {}", e);
            }
            criteria.andOperator(Criteria.where("beginTime")
                    .lte(endTime));
        }

        if (alarmInfoDto.getAlarmLevel() != null) {
            criteria.and("alarmLevel").is(alarmInfoDto.getAlarmLevel());
        }

        if (alarmInfoDto.getAlarmIds() != null && !alarmInfoDto.getAlarmIds().isEmpty()) {
            criteria.and("alarmId").in(alarmInfoDto.getAlarmIds());
        }

        if (alarmInfoDto.getLastReportBeginTime() != null &&
                alarmInfoDto.getLastReportEndTime() != null) {
            criteria.and("lastReportDate")
                    .gte(alarmInfoDto.getLastReportBeginTime())
                    .lte(alarmInfoDto.getLastReportEndTime());
        } else if (alarmInfoDto.getLastReportBeginTime() != null) {
            criteria.and("lastReportDate").gte(alarmInfoDto.getLastReportBeginTime());
        } else if (alarmInfoDto.getLastReportEndTime() != null) {
            criteria.and("lastReportDate").lte(alarmInfoDto.getLastReportEndTime());
        }

        if (alarmInfoDto.getCompanyId() != null) {
            criteria.and("companyId").is(alarmInfoDto.getCompanyId());
        }

        if (alarmInfoDto.getMeAuditStatus() != null) {
            criteria.and("meAuditStatus").is(alarmInfoDto.getMeAuditStatus());
        } else if (alarmInfoDto.getNeMeAuditStatus() != null) {
            criteria.and("meAuditStatus").ne(alarmInfoDto.getNeMeAuditStatus());
        }

        if (alarmInfoDto.getFenceIds() != null && !alarmInfoDto.getFenceIds().isEmpty()) {
            criteria.and("fenceInfo.fenceId").in(alarmInfoDto.getFenceIds());
        }
        Query query = new Query(criteria);
        query.with(new Sort(Sort.Direction.DESC, "beginTime"));
        return query;
    }

    /**
     * 转换DTO page
     *
     * @param page
     * @return
     */
    private PageExtend<AlarmInfoResDto> convert(PageExtend<AlarmInfo> page) {

        List<AlarmInfoResDto> alarmInfoDtos = convertDto(page.getRecords());

        PageExtend<AlarmInfoResDto> pageDto = new PageExtend<>();
        pageDto.setTotal(page.getTotal());
        pageDto.setPageSize(page.getPageSize());
        pageDto.setPageNumber(page.getPageNumber());
        pageDto.setRecords(alarmInfoDtos);

        return pageDto;
    }

    /**
     * List转换
     *
     * @param alarmInfos
     * @return
     */
    private List<AlarmInfoResDto> convertDto(List<AlarmInfo> alarmInfos) {
        List<AlarmInfoResDto> alarmInfoDtos = new ArrayList<>();
        if (alarmInfos == null) {
            return alarmInfoDtos;
        }
        alarmInfos.forEach(alarmInfo -> {
            AlarmInfoResDto alarmInfoResDto = new AlarmInfoResDto();
            AlarmLocationDto alarmLocationDto = new AlarmLocationDto();
            AlarmLocation disarmLocation = new AlarmLocation();
            AlarmMileageInfoDto alarmMileageInfoDto = new AlarmMileageInfoDto();
            AlarmVehicleInfoDto alarmVehicleInfoDto = new AlarmVehicleInfoDto();
            FenceInfoExtend fenceInfo = new FenceInfoExtend();

            CommonUtils.copyProperties(alarmInfo, alarmInfoResDto);
            CommonUtils.copyProperties(alarmInfo.getLocation(), alarmLocationDto);
            CommonUtils.copyProperties(alarmInfo.getAlarmMileageInfo(), alarmMileageInfoDto);
            CommonUtils.copyProperties(alarmInfo.getAlarmVehicleInfo(), alarmVehicleInfoDto);
            CommonUtils.copyProperties(alarmInfo.getDisarmLocation(), disarmLocation);
            CommonUtils.copyProperties(alarmInfo.getFenceInfo(), fenceInfo);

            alarmInfoResDto.setLocation(alarmLocationDto);
            alarmInfoResDto.setAlarmMileageInfo(alarmMileageInfoDto);
            alarmInfoResDto.setAlarmVehicleInfo(alarmVehicleInfoDto);
            alarmInfoResDto.setDisarmLocation(disarmLocation);
            alarmInfoResDto.setFenceInfo(fenceInfo);

            if (AlarmCodeEnum.MILEAGE_EXCEPTION_CHANGE.getFlag() == alarmInfo.getAlarmCode() &&
                    alarmInfo.getMileageExceptionChange() != null) {
                AlarmMileageExceptionExtend mileageExceptionChange = alarmInfo.getMileageExceptionChange();
                alarmInfoResDto.setDifferenceMileage(mileageExceptionChange.getDifferenceMileage() + "km");
            }

            alarmInfoDtos.add(alarmInfoResDto);
        });
        return alarmInfoDtos;
    }

    @Override
    public Long getAlarmCountByAlarmCodes(List<Integer> alarmCodes, Long companyId) {
        Query query = new Query();
        Criteria criteria = Criteria.where("status").is(AlarmStatusEnum.ALARMING.getFlag())
                .and("alarmCode").in(alarmCodes);

        if (companyId != null) {
            criteria.and("companyId").is(companyId);
        }
        query.addCriteria(criteria);
        return alarmDao.count(query);
    }

    /**
     * 里程异变处理 审核
     *
     * @param alarmMeAuditReqDto
     */
    @Override
    public void meAudit(AlarmMeAuditReqDto alarmMeAuditReqDto) {
        Query query = new Query(Criteria.where("alarmId").is(alarmMeAuditReqDto.getAlarmId()));
        Update update = new Update()
                .set("status", alarmMeAuditReqDto.getStatus())
                .set("meAuditStatus", alarmMeAuditReqDto.getMeAuditStatus())
                .set("meAuditMemo", alarmMeAuditReqDto.getMeAuditMemo())
                .set("meAuditTime", new Date())
                .set("relieveUserId", alarmMeAuditReqDto.getSysUserId())
                .set("relieveUserName", alarmMeAuditReqDto.getSysRealName());

        alarmDao.update(query, update);
    }

    /**
     * 获取vin 正在报警的数量 校验
     *
     * @param alarmInfoReqDto
     * @return
     */
    @Override
    public Map<String, Integer> getTotalAlarming(AlarmInfoReqDto alarmInfoReqDto) {

        Criteria criteria = Criteria.
                where("vin").in(alarmInfoReqDto.getVins())
                .and("status").is(AlarmStatusEnum.ALARMING.getFlag());

        Aggregation agg = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.group("vin").count().as("count"),
                Aggregation.project("count").andExclude("_id").
                        and("_id").as("vin")
        );

        AggregationResults<Map> results = mongoTemplate.aggregate(agg, "alarmInfo", Map.class);

        Map<String, Integer> dataMap = new HashMap<>();
        for (Map map : results) {
            String vin = String.valueOf(map.get("vin"));
            Integer count = Integer.parseInt(String.valueOf(map.get("count")));
            dataMap.put(vin, count);
        }

        return dataMap;
    }

    @Override
    public void relieve(AlarmInfoReqDto alarmInfoReqDto) {
        Query query = new Query(Criteria.where("alarmId").is(alarmInfoReqDto.getAlarmId()));
        Update update = new Update()
                .set("status", alarmInfoReqDto.getStatus())
                .set("updateTime", new Date())
                .set("isHandle", alarmInfoReqDto.getIsHandle());
        alarmDao.update(query, update);
    }

    @Override
    public AlarmInfoResDto getAlarmInfo(String alarmId) {
        Query query = new Query(Criteria.where("alarmId").is(alarmId));
        List<AlarmInfo> alarmInfos = alarmDao.find(query);
        if (CollectionUtils.isEmpty(alarmInfos)) {
            return null;
        }
        List<AlarmInfoResDto> alarmInfoDtos = convertDto(alarmInfos);
        return alarmInfoDtos.get(0);
    }
}
